﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Navigation;
using System.Xml.Linq;
using CuteRibs.D3Armory.LiveAsyncExtensions;
using CuteRibs.D3Armory.Models;
using CuteRibs.D3Armory.Resources;
using CuteRibs.D3Armory.ViewModels;
using CuteRibs.MVVMLib;
using ICSharpCode.SharpZipLib.GZip;
using Microsoft.Live;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Tasks;
using Newtonsoft.Json;
using System.Text;
using System.Xml;

namespace CuteRibs.D3Armory
{
	public partial class App : AppBase
	{
		#region tooltipHtml
		private static readonly string tooltipHtml = @"
<html xmlns=""http://www.w3.org/1999/xhtml"">
<head>
	<title></title>
	<meta http-equiv=""X-UA-Compatible"" content=""IE=edge,chrome=1"" />
	<meta name=""viewport"" content=""width=354"" />
	<link href=""http://{0}/d3/static/css/tooltips.css"" rel=""stylesheet"" type=""text/css"" />
	<script type=""text/javascript"">
		window.onload = function () {{
			var content = document.getElementById('content');
			window.external.Notify(content.scrollHeight.toString());
		}}
	</script>
</head>
<body style=""background-color: #000000; margin: 0; padding: 0;"">
<div id=""content"">
{1}
</div>
</body>
</html>
";
		#endregion

		private static readonly string realmStatusUri = "http://{0}/d3/{1}/status";
		private static readonly string profileUri = "http://{0}/api/d3/profile/{1}/index?locale={2}";
		private static readonly string heroUri = "http://{0}/api/d3/profile/{1}/hero/{2}?locale={3}";
		private static readonly string itemUri = "http://{0}/api/d3/data/{1}?locale={2}";

		private static readonly string itemTooltipUri = "http://{0}/d3/{1}/tooltip/{2}";
		private static readonly string runeTooltipUri = "http://{0}/d3/{1}/tooltip/{2}";
		private static readonly string skillTooltipUri = "http://{0}/d3/{1}/tooltip/{2}?runeType={3}";

		private static readonly string jsonProfilesFilePath = "/profiles.json";
		private static readonly string jsonProfileFilePath = "/Profiles/{0}/{1}/profile.json";
		private static readonly string jsonHeroFilePath = "/Profiles/{0}/{1}/{2}.json";
		private static readonly string imageFilePath = "/Images/{0}.png";
		private static readonly string itemFilePath = "/Items/{0}";

		private static readonly string itemLargeImageUri = "http://us.media.blizzard.com/d3/icons/items/large/{0}.png";
		private static readonly string itemSmallImageUri = "http://us.media.blizzard.com/d3/icons/items/small/{0}.png";
		private static readonly string skillLargeImageUri = "http://us.media.blizzard.com/d3/icons/skills/64/{0}.png";
		private static readonly string skillSmallImageUri = "http://us.media.blizzard.com/d3/icons/skills/42/{0}.png";

		private static List<ProfileBrief> currentProfiles;
		private static Realm[] realms;
		public static bool IsSplashScreenLoaded;

		public static List<ProfileBrief> CurrentProfiles
		{
			get
			{
				if (currentProfiles == null)
				{
					LoadProfiles();
				}

				return currentProfiles;
			}
		}

		public static Realm[] Realms
		{
			get
			{
				if (realms == null)
				{
					realms = new Realm[] { Realm.Americas, Realm.Europe, Realm.Asia };

					foreach (var realm in realms)
					{
						realm.Name = App.GetRealmName(realm);
					}
				}

				return realms;
			}
		}

		#region application life circle

		public App()
			: base()
		{
			this.IsErrorAlertEnabled = true;
			this.InitializeComponent();
		}

		protected override void OnAppLaunching(LaunchingEventArgs e)
		{
			base.OnAppLaunching(e);

			//Location = Settings.GetSettingValue<string>(App.SettingKeyLocation, "上海市");
			//BackgoundPlay = Settings.GetSettingValue<bool>(App.SettingKeyBackgoundPlay, true);
			//SelectedFrequency = (int)(FMRadio.Instance.Frequency * 10);
		}

		protected override void OnAppActivated(ActivatedEventArgs e)
		{
			base.OnAppActivated(e);

			IsSplashScreenLoaded = true;
		}

		protected override void OnAppClosing(ClosingEventArgs e)
		{
			base.OnAppClosing(e);

			SaveProfiles();
		}

		protected override void OnRootFrameNavigationFailed(NavigationFailedEventArgs e)
		{
			base.OnRootFrameNavigationFailed(e);
		}

		protected override void OnUnhandledException(ApplicationUnhandledExceptionEventArgs e)
		{
			base.OnUnhandledException(e);
		}
		#endregion

		public static async Task<string> DownloadString(string url)
		{
			try
			{
				string content = null;
				Stream stream;
				var client = new WebClient { Encoding = Encoding.UTF8 };
				client.Headers[HttpRequestHeader.AcceptEncoding] = "gzip";
				var input = await client.OpenReadTaskAsync(url);
				var contentEncoding = client.ResponseHeaders[HttpRequestHeader.ContentEncoding];

				if (!string.IsNullOrWhiteSpace(contentEncoding) && contentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
				{
					stream = new GZipInputStream(input);
				}
				else
				{
					stream = input;
				}

				if (stream != null)
				{
					using (StreamReader reader = new StreamReader(stream))
					{
						content = reader.ReadToEnd();
					}

					stream.Close();
				}

				return content;
			}
			catch (Exception ex)
			{
				if (ex is WebException)
				{
					throw;
				}
				else
				{
					throw new AppException(ex, url);
				}
			}
		}

		private static void SaveProfiles()
		{
			SaveJsonFile(jsonProfilesFilePath, CurrentProfiles);
		}

		private static void LoadProfiles(Stream stream = null)
		{
			ProfileBrief[] profiles;

			if (stream == null)
			{
				if (App.IsDesignMode)
				{
					var streamInfo = App.GetResourceStream(new Uri("SampleData/profiles.json", UriKind.Relative));

					if (streamInfo == null) { return; }

					profiles = DeserializeJson<ProfileBrief[]>(streamInfo.Stream);
					streamInfo.Stream.Close();
				}
				else
				{
					string fileName = "profiles.json";
					profiles = LoadJsonFile<ProfileBrief[]>(fileName);
				}
			}
			else
			{
				profiles = DeserializeJson<ProfileBrief[]>(stream);
				stream.Close();
			}

			if (profiles != null)
			{
				if (currentProfiles == null)
				{
					currentProfiles = profiles.ToList();
				}
				else
				{
					foreach (var profile in profiles)
					{
						if (currentProfiles.Any(p => p.Realm.Equals(profile.Realm) && p.BattleTag.Equals(profile.BattleTag)))
						{
							continue;
						}

						currentProfiles.Add(profile);
					}
				}
			}
			else
			{
				currentProfiles = new List<ProfileBrief>();
			}
		}

		public static void DeleteProfile(Realm realm, string battleTag)
		{
			var profile = CurrentProfiles.FirstOrDefault(p => p.Realm.Equals(realm) && p.BattleTag.Tag.Equals(battleTag));

			if (profile != null)
			{
				CurrentProfiles.Remove(profile);

				var fileName = string.Format(jsonProfileFilePath, profile.Realm.Region, profile.BattleTag.Tag);

				using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
				{
					if (storage.FileExists(fileName))
					{
						var directory = Path.GetDirectoryName(fileName);
						storage.DeleteWholeDirectory(directory);
					}
				}
			}
		}

		public static async Task<Profile> LoadProfile(Realm realm, string battleTag)
		{
			var fileName = string.Format(jsonProfileFilePath, realm.Region.ToString(), battleTag);
			var profile = LoadJsonFile<Profile>(fileName);

			if (profile == null)
			{
				profile = await GetProfile(realm, battleTag);
			}
			else
			{
				profile.Realm = realm;
			}

			return profile;
		}

		public static async Task<Profile> GetProfile(Realm realm, string battleTag, bool autoSave = true)
		{
			var uri = string.Format(profileUri, App.GetRealmHost(realm), battleTag, App.GetLanguage());
			var json = await DownloadString(uri);
			var profile = JsonConvert.DeserializeObject<Profile>(json);

			if (profile != null && profile.ErrorCode == null)
			{
				profile.Realm = realm;
				SaveProfile(profile);
			}

			return profile;
		}

		public static void SaveProfile(Profile profile)
		{
			var profileBrief = CurrentProfiles.FirstOrDefault(p => p.Realm.Equals(profile.Realm) && p.BattleTag.Equals(profile.BattleTag));

			if (profileBrief == null)
			{
				CurrentProfiles.Add(profile.CreateBrief());
			}

			string fileName = string.Format(jsonProfileFilePath, profile.Realm.Region, profile.BattleTag.Tag);
			SaveJsonFile(fileName, profile);
			SaveProfiles();
		}

		private static void SetItemSlots(Hero hero)
		{
			ItemSlotAttribute.SetItemSlot(hero.Items);

			if (hero.Followers.Templar != null)
			{
				ItemSlotAttribute.SetItemSlot(hero.Followers.Templar.Items);
			}

			if (hero.Followers.Scoundrel != null)
			{
				ItemSlotAttribute.SetItemSlot(hero.Followers.Scoundrel.Items);
			}

			if (hero.Followers.Enchantress != null)
			{
				ItemSlotAttribute.SetItemSlot(hero.Followers.Enchantress.Items);
			}
		}

		public static async Task<Hero> LoadHero(string region, string battleTag, uint id)
		{
			string fileName = string.Format(jsonHeroFilePath, region, battleTag, id);
			Hero hero = LoadJsonFile<Hero>(fileName);
			var realm = Realm.Parse(region);

			if (hero == null)
			{
				hero = await GetHero(realm, battleTag, id);
			}
			else
			{
				SetItemSlots(hero);
			}

			return hero;
		}

		public static async Task<Hero> GetHero(Realm realm, string battleTag, uint heroId)
		{
			var uri = string.Format(heroUri, App.GetRealmHost(realm), battleTag, heroId, App.GetLanguage());
			var json = await DownloadString(uri);
			var hero = JsonConvert.DeserializeObject<Hero>(json);

			if (hero != null && hero.ErrorCode == null)
			{
				SetItemSlots(hero);
				SaveHero(realm.Region.ToString(), battleTag, hero);
			}

			return hero;
		}

		public static void SaveHero(string region, string battleTag, Hero hero)
		{
			string fileName = string.Format(jsonHeroFilePath, region, battleTag, hero.Id);
			SaveJsonFile(fileName, hero);
		}

		public static async Task<Item> LoadItem(ItemBrief itemBrief)
		{
			var filePath = string.Format(itemFilePath, Helper.GetSHA1(itemBrief.TooltipParams));
			Item item = LoadJsonFile<Item>(filePath);

			if (item == null)
			{
				item = await GetItem(itemBrief);
			}
			else
			{
				item.ItemSlot = itemBrief.ItemSlot;
			}

			return item;
		}

		public static async Task<Item> GetItem(ItemBrief itemBrief)
		{
			var uri = string.Format(itemUri, App.GetRealmHost(), itemBrief.TooltipParams, App.GetLanguage());
			var json = await DownloadString(uri);
			var item = JsonConvert.DeserializeObject<Item>(json);

			if (item != null && item.ErrorCode == null)
			{
				item.ItemSlot = itemBrief.ItemSlot;
				SaveItem(item);
			}

			return item;
		}

		public static void SaveItem(Item item)
		{
			if (App.IsDesignMode)
			{
				return;
			}

			var filePath = string.Format(itemFilePath, Helper.GetSHA1(item.TooltipParams));
			SaveJsonFile(filePath, item);
		}

		public static async Task<string> LoadItemTooltip(string tooltipParams, bool refresh = false)
		{
			string host = App.GetRealmHost();
			string uri = string.Format(itemTooltipUri, host, App.GetLanguage(host), tooltipParams);

			if (refresh)
			{
				return await LoadTooltip(host, uri);
			}

			return await GetTooltip(host, uri);
		}

		public static async Task<string> LoadSkillTooltip(string tooltipParams, string runeType, bool refresh = false)
		{
			string host = App.GetRealmHost();
			string uri = string.Format(skillTooltipUri, host, App.GetLanguage(host), tooltipParams, runeType);

			if (refresh)
			{
				return await LoadTooltip(host, uri);
			}

			return await GetTooltip(host, uri);
		}

		public static async Task<string> LoadRuneTooltip(string tooltipParams, bool refresh = false)
		{
			string host = App.GetRealmHost();
			string uri = string.Format(runeTooltipUri, host, App.GetLanguage(host), tooltipParams);

			if (refresh)
			{
				return await LoadTooltip(host, uri);
			}

			return await GetTooltip(host, uri);
		}

		public static async Task<string> LoadTooltip(string host, string uri)
		{
			var filePath = string.Format(itemFilePath, Helper.GetSHA1(uri));
			string html = null;

			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				if (storage.FileExists(filePath))
				{
					using (var stream = storage.OpenFile(filePath, FileMode.Open))
					{
						using (var reader = new StreamReader(stream))
						{
							html = reader.ReadToEnd();
						}
					}
				}
			}

			if (html == null)
			{
				html = await GetTooltip(host, uri);
			}

			return html;
		}

		public static async Task<string> GetTooltip(string host, string uri)
		{
			var html = await DownloadString(uri);

			if (!string.IsNullOrWhiteSpace(html))
			{
				html = Helper.EncodeHtmlString(string.Format(tooltipHtml, host, html));
				App.SaveTooltip(uri, html);
			}

			return html;
		}

		public static void SaveTooltip(string uri, string html)
		{
			if (App.IsDesignMode)
			{
				return;
			}

			var filePath = string.Format(itemFilePath, Helper.GetSHA1(uri));

			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				storage.EnsureDirectory(filePath);

				using (var fileStream = storage.CreateFile(filePath))
				{
					using (var writer = new StreamWriter(fileStream))
					{
						writer.Write(html);
					}
				}
			}
		}

		public static List<RealmStatus> ParseRealmStatus(string html)
		{
			var realms = new List<RealmStatus>();

			try
			{
				var xDoc = XDocument.Parse(html);
				var xRealms = xDoc.Descendants("{http://www.w3.org/1999/xhtml}div").Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "box");

				foreach (var xRealm in xRealms)
				{
					var realm = new RealmStatus { Name = xRealm.Elements().ElementAt(0).Value.Trim(), Servers = new List<ServerStatus>() };

					foreach (var xServer in xRealm.Descendants("{http://www.w3.org/1999/xhtml}div").Where(e => e.Attribute("class") != null && (e.Attribute("class").Value == "server" || e.Attribute("class").Value == "server alt")))
					{
						var server = new ServerStatus { Name = xServer.Elements().ElementAt(1).Value.Trim() };

						if (xServer.Elements().ElementAt(0).Attribute("class").Value.EndsWith("up"))
						{
							server.Status = "up";
						}
						else
						{
							server.Status = "down";
						}

						realm.Servers.Add(server);
					}

					realms.Add(realm);
				}
			}
			catch (Exception ex)
			{
				if (ex is XmlException)
				{
					App.ShowAlert(AppRes.MsgDataAccessError);
				}
				else
				{
					App.ShowError(ex);
				}
			}

			return realms;
		}

		public static async Task<List<RealmStatus>> GetRealmStatus()
		{
			var host = App.GetRealmHost();
			var uri = string.Format(realmStatusUri, App.GetRealmHost(), App.GetLanguage(host));
			var html = await DownloadString(uri);
			return ParseRealmStatus(html);
		}

		private static readonly string liveClientId = "00000000400D7570";
		private static readonly string[] liveScopes = { "wl.skydrive", "wl.skydrive_update" };
		private static LiveAuthClient authClient;
		private static LiveConnectClient connectClient;

		private static LiveAuthClient AuthClient
		{
			get
			{
				if (authClient == null)
				{
					authClient = new LiveAuthClient(liveClientId);
				}

				return authClient;
			}
		}

		private static bool IsLiveSessionExpired()
		{
			if (connectClient == null || connectClient.Session.Expires.LocalDateTime < DateTime.Now)
			{
				return true;
			}

			return false;
		}

		private static async Task<IDictionary<string, object>> GetBackupFolder(LiveConnectClient client)
		{
			var result = await client.GetAsyncTask("me/skydrive/files");

			foreach (IDictionary<string, object> f in (IEnumerable)result["data"])
			{
				if (f["name"].Equals("D3PB") && f["type"].Equals("folder") && f.ContainsKey("id"))
				{
					return f;
				}
			}

			return null;
		}

		public static async Task<bool> BackupToCloud()
		{
			if (CurrentProfiles == null || CurrentProfiles.Count == 0)
			{
				throw new ArgumentNullException(AppRes.MsgNoProfileToBackup);
			}

			if (IsLiveSessionExpired())
			{
				var result = await AuthClient.LoginAsyncTask(liveScopes);

				if (result.Status != LiveConnectSessionStatus.Connected)
				{
					throw new Exception(AppRes.MsgLiveCannotConnect);
				}

				connectClient = new LiveConnectClient(result.Session);
			}

			var folder = await GetBackupFolder(connectClient);

			if (folder == null)
			{
				folder = new Dictionary<string, object>();
				folder.Add("name", "D3PB");
				folder.Add("description", "Diablo 3 Armory Profile Backup");
				folder = await connectClient.PostAsyncTask("me/skydrive", folder);
			}

			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				if (storage.FileExists(jsonProfilesFilePath))
				{
					using (var stream = storage.OpenFile(jsonProfilesFilePath, FileMode.Open))
					{
						var file = await connectClient.UploadAsyncTask(folder["id"] as string, "profiles.json", stream);
						return true;
					}
				}
			}

			return false;
		}

		public static async Task<bool> RestoreFromCloud()
		{
			if (IsLiveSessionExpired())
			{
				var result = await AuthClient.LoginAsyncTask(liveScopes);

				if (result.Status != LiveConnectSessionStatus.Connected)
				{
					throw new Exception(AppRes.MsgLiveCannotConnect);
				}

				connectClient = new LiveConnectClient(result.Session);
			}

			var folder = await GetBackupFolder(connectClient);

			var files = await connectClient.GetAsyncTask((string)folder["id"] + "/files");
			IDictionary<string, object> file = null;

			foreach (IDictionary<string, object> f in (IEnumerable)files["data"])
			{
				if (f["name"].Equals("profiles.json"))
				{
					file = f;
					break;
				}
			}

			if (file != null)
			{
				var stream = await connectClient.DownloadAsyncTask((string)file["id"] + "/content");
				LoadProfiles(stream);
				return true;
			}

			return false;
		}

		public static string GetRealmName(Realm realm)
		{
			string name;

			switch (realm.Region)
			{
				case Region.Europe:
					name = AppRes.RealmEurope;
					break;
				case Region.Asia:
					name = AppRes.RealmAsia;
					break;
				case Region.Americas:
				default:
					name = AppRes.RealmAmericas;
					break;
			}

			return name;
		}

		public static string GetRealmHost(Realm realm = null)
		{
			var langName = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
			string domain;

			if (realm == null)
			{
				switch (langName)
				{
					case "zh":
						domain = "tw.battle.net";
						break;
					case "ko":
						domain = "kr.battle.net";
						break;
					case "en":
						domain = "us.battle.net";
						break;
					default:
						domain = "eu.battle.net";
						break;
				}
			}
			else
			{
				switch (realm.Region)
				{
					case Region.Americas:
						domain = "us.battle.net";
						break;
					case Region.Asia:
						domain = langName.StartsWith("ko") ? "kr.battle.net" : "tw.battle.net";
						break;
					case Region.Europe:
					default:
						domain = "eu.battle.net";
						break;
				}
			}

			return domain;
		}

		public static string GetLanguage(string host = null)
		{
			var langName = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;

			if (host == null)
			{
				if (!langName.In("zh", "ko", "pt", "de", "es", "fr", "pl", "it", "ru"))
				{
					langName = "en";
				}
			}
			else
			{
				if ((host.IsBetween("tw.battle.net", "kr.battle.net") && !langName.In("zh", "ko"))
					|| (host == "us.battle.net" && !langName.In("es", "pt"))
					|| !langName.In("de", "es", "fr", "pl", "it", "ru"))
				{
					langName = "en";
				}
			}

			return langName;
		}

		public static string GetItemBackgroundUri(string classId, byte gender)
		{
			return string.Format("/Resources/Images/{0}-{1}.jpg", classId, gender == 0 ? "male" : "female");
		}

		public static string GetItemImageUri(string icon)
		{
			return string.Format(itemLargeImageUri, icon);
		}

		public static string GetGemImageUri(string icon)
		{
			return string.Format(itemSmallImageUri, icon);
		}

		public static string GetSkillImageUri(string icon, bool isSmall = false)
		{
			string uri = isSmall ? skillSmallImageUri : skillLargeImageUri;
			//string localUri = string.Format(imageFilePath, icon);

			//if (!App.IsDesignMode)
			//{
			//    if (FileExists(localUri))
			//    {
			//        return localUri;
			//    }
			//}

			return string.Format(uri, icon);
		}

		public static T DeserializeJson<T>(Stream stream)
		{
			using (var reader = new StreamReader(stream))
			{
				return JsonConvert.DeserializeObject<T>(reader.ReadToEnd());
			}
		}

		public static void SaveJsonFile(string filePath, object obj)
		{
			if (App.IsDesignMode)
			{
				return;
			}

			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				storage.EnsureDirectory(filePath);

				using (var fileStream = storage.CreateFile(filePath))
				{
					using (var writer = new StreamWriter(fileStream))
					{
						writer.Write(JsonConvert.SerializeObject(obj));
					}
				}
			}
		}

		public static T LoadJsonFile<T>(string fileName)
		{
			if (App.IsDesignMode)
			{
				return default(T);
			}

			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				if (storage.FileExists(fileName))
				{
					using (var stream = storage.OpenFile(fileName, FileMode.Open))
					{
						return DeserializeJson<T>(stream);
					}
				}
			}

			return default(T);
		}

		public static void ClearCache()
		{
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
			{
				var folders = new string[] { "Profiles", "Images", "Tooltips" };

				foreach (var folder in folders)
				{
					storage.DeleteWholeDirectory(folder);
				}
			}
		}

		public static void ShowAlert(string message)
		{
			MessageBox.Show(message ?? string.Empty, AppRes.AppName, MessageBoxButton.OK);
		}

		public static MessageBoxResult ShowConfirm(string message)
		{
			return MessageBox.Show(message, AppRes.AppName, MessageBoxButton.OKCancel);
		}

		public static void ShowError(Exception ex)
		{
			if (ex == null)
			{
				return;
			}

			var message = string.Format(AppRes.MsgErrorOccurred, ex.Message);

			if (ShowConfirm(message) == MessageBoxResult.OK)
			{
				EmailComposeTask task = new EmailComposeTask();
				task.To = "ericfine2012@live.com";
				task.Bcc = "48657539@qq.com";
				task.Subject = string.Format("{0} v{1} ({2}) Error Report", AppRes.AppName, Coding4Fun.Phone.Controls.Data.PhoneHelper.GetAppAttribute("Version"), CultureInfo.CurrentCulture.Name);
				task.Body = ex.ToString();
				task.Show();
				ShowAlert(AppRes.MsgThankYou);
			}
		}
	}
}